/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.httpclient;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;

import org.zhiqim.kernel.model.codes.MD5;
import org.zhiqim.kernel.model.maps.HashMapSO;
import org.zhiqim.kernel.model.maps.MapSO;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.DateTimes;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Urls;
import org.zhiqim.kernel.util.Validates;

/**
 * HTTP远程方法调用，指定类和方法名，提供方法顺序参数值，返回状态和文本
 * 请求参数：
 * 1.className              类名:org.zhiqim.example.IndexAction
 * 2.methodName             方法名:doTest
 * 3.params                 要求传入实参和方法形参1)顺序相同，2)类型可转化
 * 返回结果：
 * 4.responseStatus 对应conn.getResponseCode()，状态码:
 *      4.1)200             表示成功，responseText为要求的文本数据
 *      4.2)4XX             表示服务端禁止调用
 *      4.3)5XX             表示服务端内部异常
 *      4.4)601,602,603     表示三种不同的重定义，分别为parent,self,top
 *      4.5)6XX             表示客户端调用异常
 *      4.6)7XX或小于70     表示自定义错误码
 * 5.responseText 文本数据，对应conn.getResponseMessage()。
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 * @version v1.3.0 @author zouzhigang 2017-3-15 增加对表单方式的提交
 */
public class HttpRMI extends HttpClient
{
    private String key;
    private String secret;
    
    private String className;
    private String methodName;
    private String serviceId;
    
    private List<String> params;
    private MapSO paramMap;
    private String queryString;
    
    //601,602,603时解析成重定向和错误信息
    private String redirect;
    private String error;
    
    public HttpRMI(String url)
    {
        super(url, _POST_);
    }
    
    public void setKeySecret(String key, String secret)
    {
        this.key = key;
        this.secret = secret;
    }
    
    public void setClassName(String className)
    {
        this.className = className;
    }
    
    public void setMethodName(String methodName)
    {
        this.methodName = methodName;
    }
    
    public void setServiceId(String serviceId)
    {
        this.serviceId = Strings.trim(serviceId);
    }

    /**
     * 按V方式增加参数，值为null会报错
     * 
     * @param param     参数值
     */
    public void addParam(Object param)
    {
        Asserts.as(param != null?null:"不支持按值方式传入null值");
        
        if (params == null)
            params = new ArrayList<String>();
        params.add(String.valueOf(param));
    }
    
    /**
     * 按K-V方式增加参数，值为null，不传到服务端
     * 
     * @param name      参数名称
     * @param value     参数值
     */
    public void addParam(String name, Object value)
    {
        if (value == null)
            return;
        
        if (paramMap == null)
            paramMap = new HashMapSO(3);
        paramMap.put(name, value);
    }
    
    public String getRedirect()
    {
        return redirect;
    }

    public String getError()
    {
        return error;
    }

    protected boolean doPreRequestProperty()
    {
        if ((Validates.isEmptyBlank(serviceId) && Validates.isEmptyBlank(className)) || Validates.isEmptyBlank(methodName) 
            || Validates.isEmptyBlank(key) || Validates.isEmptyBlank(secret))
        {//基础5个参数4项必须填入，否则认为是非法请求
            responseStatus = _72_PARAM_NOT_SATISFIED_;
            responseText = "必须的参数未满足";
            return false;
        }
        
        String clazz = Strings.trim(className, _EMPTY_);
        String service = Strings.trim(serviceId, _EMPTY_);
        String timestamp = DateTimes.getDateTimeString();
        String authorization = MD5.encodeUTF8(key + timestamp + clazz + methodName + service + secret);
        
        addRequestProperty(_X_RMI_KEY_, key);
        addRequestProperty(_X_RMI_CLASS_, clazz);
        addRequestProperty(_X_RMI_METHOD_, methodName);
        addRequestProperty(_X_RMI_SERVICE_, service);
        addRequestProperty(_X_RMI_TIMESTAMP_, timestamp);
        addRequestProperty(_X_RMI_AUTHORIZATION_, authorization);
        
        if (paramMap != null)
        {//表单方式
            queryString = Urls.toQueryString(paramMap);
            addRequestProperty(_CONTENT_TYPE_, _APPLICATION_X_WWW_FORM_UTF_8_);
        }
        else if (params != null)
        {//参数方式
            StringBuilder strb = new StringBuilder();
            for (String param : params)
            {
                if (param.indexOf('#') != -1)
                    param = param.replaceAll("#", "-%2-%-3%-"); //把#先替换成-%2-%-3%-
                
                strb.append("%23").append(Urls.encodeUTF8(param)).append("%23");
            }
            
            queryString = strb.toString();
            addRequestProperty(_CONTENT_TYPE_, _TEXT_PLAIN_UTF_8_);
        }
    
        if (Validates.isNotEmpty(queryString))
        {
            setDoOutput(true);
            addRequestProperty(_CONTENT_LENGTH_, Bytes.getByteLen(queryString, _UTF_8_));
        }
        else
        {//为空也认为是TEXT
            addRequestProperty(_CONTENT_TYPE_, _TEXT_PLAIN_UTF_8_);
        }
        return true;
    }
    
    protected void doWriteRequestContent(HttpURLConnection conn) throws IOException
    {
        if (Validates.isEmpty(queryString))
            return;
        
        conn.getOutputStream().write(queryString.getBytes(_UTF_8_));
    }
    
    protected void doReadResponseContent(HttpURLConnection conn) throws IOException
    {
        if (responseStatus == _302_FOUND_)
        {//重定向
            responseText = conn.getHeaderField("Location");
            return;
        }
        
        String charset = getResponseCharset();
        if (responseStatus != _200_OK_ && 
            responseStatus != _601_REDIRECT_PARENT_ && responseStatus != _602_REDIRECT_ && responseStatus != _603_REDIRECT_TOP_)
        {//错误时读取responseMessage
            responseText = Urls.decode(conn.getResponseMessage(), charset);
            if (Validates.isEmpty(responseText))
                responseText = "调用服务端接口失败，错误码："+responseStatus;
            return;
        }
        
        responseText = getResponseAsString(conn);
        if (responseStatus == _601_REDIRECT_PARENT_ || responseStatus == _602_REDIRECT_ || responseStatus == _603_REDIRECT_TOP_)
        {
            if (responseText == null)
            {
                responseText = "调用服务端接口失败，要求重定向["+responseStatus+"]，但内容为NULL";
                responseStatus = _699_ERROR_;
            }
            else
            {
                int ind = responseText.lastIndexOf("#");
                if (ind == -1)
                    redirect = responseText;
                else
                {
                    error = responseText.substring(ind+1);
                    redirect = responseText.substring(0, ind);
                }
            }
        }
    }
}
